using System;
using System.Collections.Generic;
using System.IO;
using System.Text.RegularExpressions;
using TLDRML.Tokens;

namespace TLDRML
{
    public class Lexer : ILexer
    {
        private readonly TextReader _textIn;
        readonly Regex _endOfLineRegex = new Regex(@"\r\n|\r|\n", RegexOptions.Compiled);
        private readonly IList<TokenDefinition> _tokenDefinitions;
        private readonly Stack<int> _indents = new Stack<int>();

        public Lexer(TextReader textIn)
        {
            _textIn = textIn;

            _tokenDefinitions =
                new List<TokenDefinition>
                {
                    new EqualsTokenDefinition(),
                    new LiteralTokenDefinition(),
                    new StringTokenDefinition(),
                    new WhiteSpaceTokenDefinition(),
                    new NameDeclarationTokenDefinition()
                };

            _indents.Push(0);
        }

        public IEnumerable<Token> Tokenize()
        {
            string source = _textIn.ReadToEnd()
                .TrimEnd(' ');

            int currentIndex = 0;
            int currentLine = 1;
            int currentColumn = 0;

            while (currentIndex < source.Length)
            {
                TokenDefinition matchedDefinition = null;
                int matchLength = 0;

                foreach (var rule in _tokenDefinitions)
                {
                    var match = rule.Regex.Match(source, currentIndex);

                    if (match.Success && (match.Index - currentIndex) == 0)
                    {
                        matchedDefinition = rule;
                        matchLength = match.Length;
                        break;
                    }
                }

                if (matchedDefinition == null)
                    throw new ApplicationException(string.Format("Unrecognized symbol '{0}' at index {1} (line {2}, column {3}).", source[currentIndex], currentIndex, currentLine, currentColumn));

                var value = source.Substring(currentIndex, matchLength);

                if (!matchedDefinition.IsIgnored)
                    yield return matchedDefinition.Create(new TokenPosition(currentIndex, currentLine, currentColumn), value);

                var endOfLineMatch = _endOfLineRegex.Match(value);

                if (endOfLineMatch.Success)
                {
                    yield return new NewLineToken(new TokenPosition(currentIndex, currentLine, currentColumn));

                    currentLine += GetLinesFromWhiteSpace(value);
                    currentColumn = GetColumnFromWhiteSpace(value);

                    int curIndentLevel = _indents.Peek();

                    if (currentColumn > curIndentLevel)
                    {
                        _indents.Push(currentColumn);
                        yield return new IndentToken(new TokenPosition(currentIndex, currentLine, currentColumn));
                    }

                    if (currentColumn < curIndentLevel)
                    {
                        while (_indents.Peek() != currentColumn)
                        {
                            _indents.Pop();
                            yield return new OutdentToken(new TokenPosition(currentIndex, currentLine, currentColumn));
                        }
                    }
                }
                else
                    currentColumn += matchLength;

                currentIndex += matchLength;
            }
        }

        private static int GetLinesFromWhiteSpace(string value)
        {
            return value.Split('\n').Length;
        }

        private static int GetColumnFromWhiteSpace(string value)
        {
            int lastNewLine = value.LastIndexOf('\n');
            return (value.Length - lastNewLine) - 1;
        }
    }
}